#include "grate_test_utils.h"
#include "grate/util/compact_queue.h"

#include "grate/util/common.h"
#include "grate/util/word.h"

using growth_rate::CompactQueue;
using growth_rate::IncreasingLengthWordQueue;
using growth_rate::Word;

namespace {

// Test that capacity is calculated and size is maintained correctly
// as we push and pop elements.
TEST(CompactQueueTest, CapacityAndSize) {
  size_t cap_in_bytes = 5;
  int item_size = 3;
  CompactQueue queue(cap_in_bytes, item_size);
  size_t capacity = (5 * 8) / 3;
  EXPECT_TRUE(queue.is_empty());
  EXPECT_EQ(cap_in_bytes, queue.get_capacity_in_bytes());
  EXPECT_EQ(capacity, queue.get_capacity_in_items());
  // Fill and empty the queue several times.
  for (int iter = 0; iter < 3; iter++) {
    // Fill the queue with elements.
    for (size_t i = 0; i < capacity; i++) {
      EXPECT_TRUE(queue.push_item(0));
      EXPECT_FALSE(queue.is_empty());
      EXPECT_EQ(i + 1, queue.get_size_in_items());
      EXPECT_EQ(((i + 1) * item_size + 7) / 8, queue.get_size_in_bytes());
    }
    // Make sure the queue is full.
    EXPECT_FALSE(queue.push_item(0));
    // Free the queue.
    for (size_t i = 0; i < capacity; i++) {
      EXPECT_EQ(0, queue.peek_item());
      EXPECT_EQ(0, queue.pop_item());
    }
    // Make sure the queue is now empty.
    EXPECT_TRUE(queue.is_empty());
  }
}

TEST(CompactQueueTest, ContentsTest) {
  // Check all possible sizes.
  for (int item_size = 1; item_size <= 8; item_size++) {
    CompactQueue queue(260, item_size);
    // Make sure the elements are pushed and popped correctly,
    // repeat several times to check it for different unaligned pushes.
    for (int iter = 0; iter < 50; iter++) {
      for (int item = 0; item < (1<<item_size); item++) {
        EXPECT_TRUE(queue.push_item(item));
      }
      for (int item = 0; item < (1<<item_size); item++) {
        EXPECT_EQ(item, queue.peek_item());
        EXPECT_EQ(item, queue.pop_item());
      }
      EXPECT_TRUE(queue.is_empty());
    }
    // Check that queue is pushed and dumped correctly.
    const size_t array_size = 50;
    uint8_t array[50];
    EXPECT_EQ(0U, queue.dump_to_array(array, array_size));
    for (size_t i = 0; i < array_size; i++) {
      array[i] = (i + 10) % (1 << item_size);
    }
    EXPECT_EQ(array_size, queue.push_word(
        Word::createWordFromArray(array, array_size)));
    uint8_t dumped_array[50];
    EXPECT_EQ(array_size, queue.dump_to_array(dumped_array, array_size));
    for (size_t i = 0; i < array_size; i++) {
      EXPECT_EQ(array[i], dumped_array[i]);
      EXPECT_EQ(array[i], queue.pop_item());
    }
  }
}

TEST(CompactQueueTest, PopWordTest) {
  CompactQueue queue(10, 2);
  queue.push_word("abc");
  queue.push_item(3);
  uint8_t buffer[10];
  EXPECT_EQ("abcd", queue.pop_word(buffer, 4).str());
}

TEST(CompactQueueTest, DumpTest) {
  CompactQueue queue(10, 8);
  EXPECT_EQ("[]", queue.dump());
  queue.push_item(0);
  EXPECT_EQ("[0]", queue.dump());
  queue.push_item(255);
  queue.push_item(10);
  EXPECT_EQ("[0, 255, 10]", queue.dump());
  queue.pop_item();
  EXPECT_EQ("[255, 10]", queue.dump());
}

TEST(CompactQueueTest, IncreasingLengthWordQueue) {
  IncreasingLengthWordQueue queue(100, 4);
  EXPECT_TRUE(queue.is_empty());
  queue.push_word("ab");
  EXPECT_FALSE(queue.is_empty());
  queue.push_word("ac");
  queue.push_word("abc");
  EXPECT_EQ(2, queue.head_word_length());
  uint8_t buffer[10];
  // Check that empty word is returned for too short buffer.
  EXPECT_EQ(0, queue.pop_word(buffer, 1).length());
  // Check that contents is valid.
  EXPECT_EQ("ab", queue.pop_word(buffer, 10).str());
  queue.push_word("abd");
  EXPECT_EQ("ac", queue.pop_word(buffer, 10).str());
  EXPECT_EQ(3, queue.head_word_length());
  EXPECT_EQ("abc", queue.pop_word(buffer, 10).str());
  queue.push_word("abe");
  EXPECT_EQ("abd", queue.pop_word(buffer, 3).str());
  EXPECT_EQ("abe", queue.pop_word(buffer, 3).str());
  // Queue should now be empty.
  EXPECT_TRUE(queue.is_empty());
  // Check that now we may re-use it for shorter words.
  queue.push_word("f");
  EXPECT_EQ(1, queue.head_word_length());
  EXPECT_EQ("f", queue.pop_word(buffer, 1).str());
  EXPECT_TRUE(queue.is_empty());
}

}  // namespace
